package com.reger.core.code;

import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class IdWorker {

	protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class);

	private final byte workerId;
	private final byte datacenterId;

	private final static byte l = 1;
	private final static long twepoch = 1288834974657L / l;

	private final static byte workerIdBits = 5;
	private final static byte datacenterIdBits = 5;
	private final static byte maxWorkerId = -1 ^ (-1 << workerIdBits);
	private final static byte maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
	private final static byte sequenceBits = 24;

	private final static byte workerIdShift = sequenceBits;
	private final static byte datacenterIdShift = sequenceBits + workerIdBits;
	private final static byte timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	private final static int sequenceMask = -1 ^ (-1 << sequenceBits);

	private volatile static AtomicLong sequence = new AtomicLong(0);
	private volatile static long lastTimestamp = -1L;

	public IdWorker(byte workerId, byte datacenterId) {
		// sanity check for workerId
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(String.format("工作组编号不能大于%或者小于0", maxWorkerId));
		}
		if (datacenterId > maxDatacenterId || datacenterId < 0) {
			throw new IllegalArgumentException(String.format("数据中心编号不能大于%d或者小于0", maxDatacenterId));
		}
		this.workerId = workerId;
		this.datacenterId = datacenterId;
		LOG.info(String.format("开始生成ID。时间戳左移%d位, 数据中心编号%d位, 工作组编号%d位, 序列%d位, 工作组编号%d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId));
	}

	public synchronized final long nextId() {
		long timestamp = timeGen();

		if (timestamp < lastTimestamp) {
			LOG.error(String.format("系统时间向后调了。不允许这样得操作 %d。", lastTimestamp));
			throw new RuntimeException(String.format("系统时间向后调了。 不允许生成 id在 %d毫秒前得", lastTimestamp - timestamp));
		}
		long dsequence = 0;
		if (lastTimestamp == timestamp) {
			dsequence = sequence.updateAndGet(s -> ++s & sequenceMask);
			if (dsequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			sequence.set(0);
		}

		lastTimestamp = timestamp;
		return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | dsequence;
	}

	protected long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	protected long timeGen() {
		return System.currentTimeMillis() / l;
	}

	public static void main(String[] args) {
		IdWorker worker = new IdWorker( (byte)1, (byte)1);
		System.err.println(worker.nextId());
		System.err.println(Sequence36.generate());
		long s1 = System.currentTimeMillis();
		for (int i = 0; i < 100000000; i++) {
			worker.nextId();
		}
		System.err.println(worker.nextId());
		long s2 = System.currentTimeMillis();
		System.err.println(" s2-s1= " + (s2 - s1));
		for (int i = 0; i < 100000000; i++) {
			Sequence36.generate();
		}
		System.err.println(Sequence36.generate());
		long s3 = System.currentTimeMillis();
		System.err.println(" s3-s2= " + (s3 - s2));
	}
}